package com.example.yupao.job;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.yupao.model.domain.User;
import com.example.yupao.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Date 2024/6/19 11:47
 * @description: 数据缓存预热, 也就是提前把数据缓存到 redis 中
 * .
 * @Author LittleNight
 */


@Component // 加上注解, 让spring来管理
@Slf4j
public class PreCacheJob {


    @Resource
    private UserService userService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // 使用redisson 分布式锁
    @Resource
    private RedissonClient redissonClient;

    // 重点用户.
    private List<Long> recommendUserIdList = Arrays.asList(1L, 2L);

    // 秒 分 时 日 月 年 (不要去背, 去搜索)
    @Scheduled(cron = "0 0 12 * * *") // 每天十二点执行
    public void doCacheRecommendUser() { 

//        ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
//        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        // 创建一把锁, 都是用getXXX, 然后线程都来抢这把锁
        RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock");
        // 尝试获取锁, 第一个参数是等待时间(也就是其他没抢到锁的等多久在继续抢锁), 此处我们设置为0, 也就是没抢到的不在继续抢, 第二天再抢
        // 第二个参数是锁的过期时间, -1 会触发看门狗机制
        // 第三个参数是时间单位
        try {
            if(lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                for (Long userId : recommendUserIdList) {

                    ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
                    QueryWrapper<User> queryWrapper = new QueryWrapper<>();

                    // 1. 查数据库
                    // 这里我们就假设 第一页, 20个用户
                    Page<User> page = userService.page(new Page<>(1, 20), queryWrapper);
                    // 2. 写回缓存, 设置过期时间一定要
                    String redisKey = String.format("yupao:user:recommend:%s", userId);
                    // 假如写入失败, 要有一个提示, 不要抛异常: 因为写入失败, 也应该把数据返回给用户, 30分钟
                    try {
                        opsForValue.set(redisKey, page, 1800000, TimeUnit.MILLISECONDS);
                    } catch (Exception e) {
                        // e.printStackTrace();
                        // 直接打印一个错误信息
                        log.error("redis set key error", e);
                    }
                }
            }
        } catch (InterruptedException e) {
            log.error("doCacheRecommendUser error", e);
            e.printStackTrace();
        // 释放锁的逻辑最好放在 finally 中保证一定执行
        }finally {
            // 释放锁之前, 还要判断是不是自己的锁
            // 防止在释放锁的时候，有可能先判断出是自己的锁，但这时锁过期了，
            // 另一个b 立马就抢到了锁, a 去释放锁就会释放了 b 的锁,最后还是释放了别人的锁
            if(lock.isHeldByCurrentThread()) {
                System.out.println("unlock: " + Thread.currentThread().getId());
                lock.unlock();
            }
        }


//        // 1. 查询所有用户
//        List<User> userList = userService.list();
//        // 2. 把用户 id 缓存到 redis 中
//        for (User user : userList) {
//            String redisKey = String.format("yupao:user:recommend:%s", user.getId());
//            redisTemplate.opsForValue().set(redisKey, user, 1800000, TimeUnit.MILLISECONDS);
//        }
    }
}
